﻿using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ImmlPadPlugins;
using ImmlPad.Documents;
using System.Windows.Threading;
using ImmlPad.Properties;
using ImmlPad.CodeCompletion;
using System.IO;
using System.Windows.Markup;
using System.ComponentModel;
using Imml;
using Imml.IO;
using System.Linq;


namespace ImmlPad.DocumentViews
{
    /// <summary>
    /// Interaction logic for ImmlDocumentView.xaml
    /// </summary>
    public partial class ImmlDocumentView : UserControl, IDocumentView
    {
        private ImmlSerialiser _ImmlSerialiser;

        public ImmlDocumentView()
        {
            InitializeComponent();

            //EditorInfo.Frame = ContentArea;
            //ContentArea.Loaded += new RoutedEventHandler(ContentArea_Loaded);
            //ContentArea.LoadCompleted += new LoadCompletedEventHandler(ContentArea_LoadCompleted);
            //ContentArea.ContentRendered += new EventHandler(ContentArea_ContentRendered);

            //Dispatcher.UnhandledException += new DispatcherUnhandledExceptionEventHandler(Dispatcher_UnhandledException);

            var immlPadPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ImmlPad");
            var schemaPath = System.IO.Path.Combine(immlPadPath, "imml.xsd");

            try
            {
                var schemaBytes = VastPark.FrameworkBase.IO.EmbeddedResource.GetBytes("Imml.imml.xsd", System.Reflection.Assembly.GetAssembly(typeof(ImmlElement)));
                System.IO.File.WriteAllBytes(schemaPath, schemaBytes);
            }
            catch { }

            XmlCompletionDataProvider.LoadSchema(schemaPath);

            _ImmlSerialiser = new ImmlSerialiser();
        }

        //-------------------------------------------------------------------
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------

        private static DispatcherTimer dispatcherTimer;
        private bool UnhandledExceptionRaised;

        //-------------------------------------------------------------------
        //
        //  Properties
        //
        //-------------------------------------------------------------------

        #region IsValidMarkup (DependencyProperty)

        /// <summary>
        /// description of IsValidMarkup
        /// </summary>
        public bool IsValidMarkup
        {
            get { return (bool)GetValue(IsValidMarkupProperty); }
            set { SetValue(IsValidMarkupProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for IsValidMarkup
        /// </summary>
        public static readonly DependencyProperty IsValidMarkupProperty =
            DependencyProperty.Register("IsValidMarkup", typeof(bool), typeof(ImmlDocumentView), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(IsValidMarkupChanged)));

        /// <summary>
        /// PropertyChangedCallback for IsValidMarkup
        /// </summary>
        private static void IsValidMarkupChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (obj is ImmlDocumentView)
            {
                ImmlDocumentView owner = (ImmlDocumentView)obj;

                if ((bool)args.NewValue)
                {
                    owner.HideErrorUI();
                }
                else
                {
                    owner.ShowErrorUI();
                }
            }
        }

        #endregion

        private void ReportError(Exception e)
        {
            IsValidMarkup = false;

            if (e is MarkupException)
            {
                var x = (MarkupException)e;
                ErrorLineNumber = x.LineNumber;
                ErrorLinePosition = x.LinePosition;
            }
            else
            {
                ErrorLineNumber = 0;
                ErrorLinePosition = 0;
            }

            Exception inner = e;

            while (inner.InnerException != null) inner = inner.InnerException;

            ErrorText = inner.Message;
            ErrorText = ErrorText.Replace("\r", "");
            ErrorText = ErrorText.Replace("\n", "");
            ErrorText = ErrorText.Replace("\t", "");

            // get rid of everything after "Line" if it is in the last 30 characters 
            int pos = ErrorText.LastIndexOf("Line");
            if (pos > 0 && pos > (ErrorText.Length - 50))
            {
                ErrorText = ErrorText.Substring(0, pos);
            }
        }

        private void ShowErrorUI()
        {
            ImageSource src = null;

            if (_IsInitializing)
            {
                src = Document.PreviewImage;
            }
            else
            {
                // update the error image
                //src = RenderHelper.ElementToGrayscaleBitmap(ContentArea);
                //Document.PreviewImage = src;
            }

            Color c = Color.FromArgb(255, 216, 216, 216);

            if (src is BitmapSource)
            {
                CroppedBitmap croppedSrc = new CroppedBitmap((BitmapSource)src, new Int32Rect(0, 0, 1, 1));
                byte[] pixels = new byte[4];
                croppedSrc.CopyPixels(pixels, 4, 0);
                c = Color.FromArgb(255, pixels[0], pixels[1], pixels[2]);
            }

            //ErrorOverlayImage.Source = src;
            //ErrorOverlay.Background = new SolidColorBrush(c);

            //DoubleAnimation d = (DoubleAnimation)this.FindResource("ShowErrorOverlay");
            //d.Completed += new EventHandler(ErrorOverlayAnimationCompleted);
            //if (d != null)
            //{
            //    ErrorOverlay.BeginAnimation(OpacityProperty, d);
            //}
        }

        private void HideErrorUI()
        {
            //DoubleAnimation d = (DoubleAnimation)this.FindResource("HideErrorOverlay");
            //if (d != null)
            //{
            //    ErrorOverlay.BeginAnimation(OpacityProperty, d);
            //}
        }

        #region ErrorText (DependencyProperty)

        /// <summary>
        /// description of ErrorText
        /// </summary>
        public string ErrorText
        {
            get { return (string)GetValue(ErrorTextProperty); }
            set { SetValue(ErrorTextProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for ErrorText
        /// </summary>
        public static readonly DependencyProperty ErrorTextProperty =
            DependencyProperty.Register("ErrorText", typeof(string), typeof(ImmlDocumentView), new FrameworkPropertyMetadata(default(string), new PropertyChangedCallback(ErrorTextChanged)));

        /// <summary>
        /// PropertyChangedCallback for ErrorText
        /// </summary>
        private static void ErrorTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (obj is ImmlDocumentView)
            {
                ImmlDocumentView owner = (ImmlDocumentView)obj;
                // handle changed event here
            }
        }

        #endregion

        #region ErrorLineNumber (DependencyProperty)

        /// <summary>
        /// description of ErrorLineNumber
        /// </summary>
        public int ErrorLineNumber
        {
            get { return (int)GetValue(ErrorLineNumberProperty); }
            set { SetValue(ErrorLineNumberProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for ErrorLineNumber
        /// </summary>
        public static readonly DependencyProperty ErrorLineNumberProperty =
            DependencyProperty.Register("ErrorLineNumber", typeof(int), typeof(ImmlDocumentView), new FrameworkPropertyMetadata(default(int), new PropertyChangedCallback(ErrorLineNumberChanged)));

        /// <summary>
        /// PropertyChangedCallback for ErrorLineNumber
        /// </summary>
        private static void ErrorLineNumberChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (obj is ImmlDocumentView)
            {
                ImmlDocumentView owner = (ImmlDocumentView)obj;
                // handle changed event here
            }
        }

        #endregion

        #region ErrorLinePosition (DependencyProperty)

        /// <summary>
        /// description of ErrorLinePosition
        /// </summary>
        public int ErrorLinePosition
        {
            get { return (int)GetValue(ErrorLinePositionProperty); }
            set { SetValue(ErrorLinePositionProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for ErrorLinePosition
        /// </summary>
        public static readonly DependencyProperty ErrorLinePositionProperty =
            DependencyProperty.Register("ErrorLinePosition", typeof(int), typeof(ImmlDocumentView), new FrameworkPropertyMetadata(default(int), new PropertyChangedCallback(ErrorLinePositionChanged)));

        /// <summary>
        /// PropertyChangedCallback for ErrorLinePosition
        /// </summary>
        private static void ErrorLinePositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (obj is ImmlDocumentView)
            {
                ImmlDocumentView owner = (ImmlDocumentView)obj;
                // handle changed event here
            }
        }

        #endregion

        #region PreviewImage (DependencyProperty)

        public ImageSource PreviewImage
        {
            get { return (ImageSource)GetValue(PreviewImageProperty); }
            private set { SetValue(PreviewImagePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey PreviewImagePropertyKey =
            DependencyProperty.RegisterReadOnly("PreviewImage", typeof(ImageSource), typeof(ImmlDocumentView), new UIPropertyMetadata(default(ImageSource)));
        public static readonly DependencyProperty PreviewImageProperty = PreviewImagePropertyKey.DependencyProperty;

        #endregion

        #region Scale (DependencyProperty)

        public double Scale
        {
            get { return (double)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }
        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(double), typeof(ImmlDocumentView), new FrameworkPropertyMetadata(1.0));

        #endregion


        public bool EnableRenderedView
        {
            get { return (bool)GetValue(EnableRenderedViewProperty); }
            set { SetValue(EnableRenderedViewProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EnableRenderedView.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableRenderedViewProperty =
            DependencyProperty.Register("EnableRenderedView", typeof(bool), typeof(ImmlDocumentView), new UIPropertyMetadata(Settings.Default.EnableRenderedView));


        #region EventHandlers

        private const int WM_PRINT = 0x0317;

        private void SplitterDragStarted(object sender, RoutedEventArgs e)
        {

        }

        private void SplitterDragCompleted(object sender, RoutedEventArgs e)
        {

        }

        private void EditorTextChanged(object sender, ImmlPad.Controls.TextChangedEventArgs e)
        {
            if (Document != null)
            {
                if (_IsInitializing)
                {
                    _IsInitializing = false;
                }
                else
                {
                    ClearDispatcherTimer();
                    AttemptParse();
                }
            }
        }

        private void ErrorOverlayAnimationCompleted(object sender, EventArgs e)
        {
            // once we're done fading into the "snapshot", we want to 
            // get rid of the existing content so that any really bad 
            // error (like one that is consuming memory) isn't persisted
            //ContentArea.Content = null;
        }

        private void ContentAreaRendered(object sender, EventArgs e)
        {
            //try
            //{
            //    if (IsValidMarkup)
            //    {
            //        this.Document.PreviewImage = RenderHelper.VisualToBitmap(ContentArea, (int)ContentArea.ActualWidth, (int)ContentArea.ActualHeight, null);
            //    }
            //}
            //catch
            //{
            //    // do nothing
            //}
        }

        private void LineNumberClick(object sender, RoutedEventArgs e)
        {
            Editor.SelectLine(ErrorLineNumber - 1);
            Editor.Focus();
            Editor.TextEditor.Focus();
        }

        #endregion

        private static void ClearDispatcherTimer()
        {
            if (dispatcherTimer != null)
            {
                dispatcherTimer.Stop();
                dispatcherTimer = null;
            }
        }

        private bool _IsLoaded;

        private void AttemptParse()
        {
            if (Settings.Default.EnableAutoParse)
            {
                ClearDispatcherTimer();

                TimeSpan timeout = new TimeSpan(0, 0, 0, Settings.Default.AutoParseTimeout);

                dispatcherTimer =
                    new DispatcherTimer(
                        timeout,
                        DispatcherPriority.ApplicationIdle,
                        new EventHandler(ParseCallback),
                        Dispatcher.CurrentDispatcher);
            }
        }

        private void ParseCallback(object sender, EventArgs args)
        {
            Parse(false);
        }

        #region IDocumentView Members
        public void Parse()
        {
            ClearDispatcherTimer();
            Parse(true);
        }        

        private void Parse(bool isExplicit)
        {
            ClearDispatcherTimer();

            if (Document != null && !CodeCompletionPopup.IsOpenSomewhere)
            {
                if (Document.SourceText != null)
                {
                    //MemoryStream ms = null;
                    //StreamWriter sw = null;

                    // handle the in place preparsing (this actually updates the source in the editor)
                    int index = TextEditor.CaretIndex;
                    //Document.SourceText = PreParse(Document.SourceText);
                    TextEditor.CaretIndex = index;

                    string str = Document.SourceText;                    

                    try
                    {
                        //ms = new MemoryStream(str.Length);
                        //sw = new StreamWriter(ms);
                        //sw.Write(str);
                        //sw.Flush();

                        //ms.Seek(0, SeekOrigin.Begin);

                        ParserContext pc = new ParserContext();
                        pc.BaseUri = new Uri(System.Environment.CurrentDirectory + "/");

                        //ContentArea.JournalOwnership = System.Windows.Navigation.JournalOwnership.UsesParentJournal;
                        var container = _ImmlSerialiser.Read<Imml.Scene.Container.ImmlDocument>(new MemoryStream(Encoding.UTF8.GetBytes(str)));

                        if (!_IsLoaded)
                        {
                            //_ImmlLoader.Load(container);
                            _IsLoaded = true;
                        }

                        if (_ImmlSerialiser.Errors.Any())
                        {
                            var firstError = _ImmlSerialiser.Errors[0];
                            ReportError(new MarkupException(firstError.Message, firstError.LineNumber, firstError.LinePosition));
                        }
                        else
                        {
                            IsValidMarkup = true;
                            ErrorText = null;
                            ErrorLineNumber = 0;
                            ErrorLinePosition = 0;
                            UnhandledExceptionRaised = false;                    
                        }

                        if (ImmlPad.Properties.Settings.Default.EnableAutoBackup)
                        {
                            Document.SaveBackup();
                        }
                    }

                    catch (Exception e)
                    {
                        ReportError(e);
                    }

                    finally
                    {
                        //if (sw != null)
                        //{
                        //    sw.Close();
                        //}
                    }
                }
            }
        }

        bool _IsInitializing = false;

        public void Initialize()
        {
            //IsValidMarkup = true;
            _IsInitializing = true;
            //ContentArea.Content = null;

            Parse();
        }

        public IDocument Document
        {
            get
            {
                if (this.DataContext is IDocument)
                {
                    return (IDocument)this.DataContext;
                }
                else
                {
                    return null;
                }
            }
        }

        public IEditorInfoTextEditor TextEditor
        {
            get
            {
                if (this.Editor != null && this.Editor is IEditorInfoTextEditor)
                {
                    return this.Editor;
                }
                return null;
            }
        }

        #endregion

        private bool _Loaded;

        private void This_Loaded(object sender, RoutedEventArgs e)
        {
            if (_Loaded)
                return;

            _ElementNameCombo.SelectedIndex = -1;

            _Loaded = true;
            
            TextEditor.FoldingStates = this.Document.FoldingStates;
            this.EnableRenderedView = this.Document.EnableRenderedView;
        }

        private void Editor_FoldingStatesChanged(object sender, EventArgs e)
        {
            this.Document.FoldingStates = TextEditor.FoldingStates;
        }

        internal void Dispose()
        {
            //_ImmlLoader.Dispose();
        }

        private void _ElementNameCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (!_Loaded)
                return;

            if (e.AddedItems.Count > 0)
            {
                DocumentElement element = (e.AddedItems[0] as DocumentElement);
                this.Editor.Find("Name=\"" + element.Name + "\"");
            }
        }

        private void _ElementTypeCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (!_Loaded)
                return;

            ICollectionView view = CollectionViewSource.GetDefaultView(_ElementNameCombo.ItemsSource);
            view.Refresh();
        }

        private void _ElementNameCombo_PreviewKeyUp(object sender, KeyEventArgs e)
        {
            string keyString = new KeyConverter().ConvertToString(e.Key).ToLower();

            if (keyString.Length != 1)
                return;
            
            char character = keyString[0];

            if (!Char.IsLetterOrDigit(character))
                return;
        
            //search the combo for values that begin with the alpha-numeric entry
            for (int i = 0; i < _ElementNameCombo.Items.Count; i++)
			{
                string name = (_ElementNameCombo.Items[i] as DocumentElement).Name.ToLower();

                if (name.StartsWith(character.ToString()))
                {
                    //look at the selected name, it must either be a different letter or a higher index for the selectedindex to change
                    if (!name.StartsWith(character.ToString()))
                    {
                        _ElementNameCombo.SelectedIndex = i;
                        break;
                    }
                    else if (_ElementNameCombo.SelectedIndex < i)
                    {
                        _ElementNameCombo.SelectedIndex = i;
                        break;
                    }
                }
			}
        }

        private void _DocumentElementsViewSource_Filter(object sender, FilterEventArgs e)
        {
            if (_ElementTypeCombo.SelectedIndex == 0)
            {
                e.Accepted = true;
                return;
            }

            DocumentElement element = e.Item as DocumentElement;
            e.Accepted = (element.Type == (string)_ElementTypeCombo.SelectedValue);
        }
    }   
}
